package io.github.wslxm.springbootplus2.manage.sys.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import io.github.wslxm.springbootplus2.constant.CacheKey;
import io.github.wslxm.springbootplus2.core.base.service.impl.BaseServiceImpl;
import io.github.wslxm.springbootplus2.core.enums.BaseDic;
import io.github.wslxm.springbootplus2.core.utils.XjBeanUtil;
import io.github.wslxm.springbootplus2.core.utils.XjEnumUtil;
import io.github.wslxm.springbootplus2.core.utils.XjMapSortUtil;
import io.github.wslxm.springbootplus2.core.utils.tree.XjTreeUtil;
import io.github.wslxm.springbootplus2.manage.sys.dao.SysRoleAuthDao;
import io.github.wslxm.springbootplus2.manage.sys.dao.SysRoleDao;
import io.github.wslxm.springbootplus2.manage.sys.handle.InitAuthScanningHandle;
import io.github.wslxm.springbootplus2.manage.sys.mapper.SysAuthorityMapper;
import io.github.wslxm.springbootplus2.manage.sys.model.dto.SysAuthorityDTO;
import io.github.wslxm.springbootplus2.manage.sys.model.entity.SysAuthority;
import io.github.wslxm.springbootplus2.manage.sys.model.query.SysAuthorityQuery;
import io.github.wslxm.springbootplus2.manage.sys.model.vo.SysAuthorityVO;
import io.github.wslxm.springbootplus2.manage.sys.model.vo.next.SysAuthVO;
import io.github.wslxm.springbootplus2.manage.sys.service.SysAuthorityService;
import io.github.wslxm.springbootplus2.starter.redis.lock.XjDistributedLock;
import io.github.wslxm.springbootplus2.starter.redis.util.RedisUtil;
import io.github.wslxm.springbootplus2.utils.XjAuthCacheKeyUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author wangsong
 */
@Service
@Slf4j
public class SysAuthorityServiceImpl extends BaseServiceImpl<SysAuthorityMapper, SysAuthority> implements SysAuthorityService {

    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private InitAuthScanningHandle initAuthScanningHandle;
    @Autowired
    private SysRoleDao sysRoleDao;
    @Autowired
    private SysRoleAuthDao sysRoleAuthDao;


    /**
     * 查询所有权限数据, 根据不同的端的枚举code 拼接最顶级的目录，顶级目录ID = -1
     *
     * @return void
     * @date 2019/11/25 0025 11:55
     */
    @Override
    public List<SysAuthorityVO> list(SysAuthorityQuery query) {
        List<SysAuthority> authoritys = baseMapper.list(query);
        List<SysAuthorityVO> vos = XjBeanUtil.listVo(authoritys, SysAuthorityVO.class);
        boolean isTree = ObjectUtil.defaultIfNull(query.getIsTree(), false);
        if (!isTree || vos.size() == 0) {
            return vos;
        }
        if (StringUtils.isNotBlank(query.getPid())) {
            return CollUtil.newArrayList(XjTreeUtil.nextTree(vos, query.getPid()));
        } else {
            boolean isGroup = query.getIsGroup() != null ? query.getIsGroup() : false;
            if (!isGroup) {
                return XjTreeUtil.nextTree(vos, "0").getAuthoritys();
            }
            // 设置二级分组字段
            for (SysAuthorityVO vo : vos) {
                vo.setGroupName(getGroupName(vo.getUrl()));
            }
            List<SysAuthorityVO> authoritysVos = XjTreeUtil.nextTree(vos, "0").getAuthoritys();
            // 设置第一层分组（根据 AuthorityType 接口类型）
            Map<Integer, List<SysAuthorityVO>> authGroup = authoritysVos.stream().collect(Collectors.groupingBy(SysAuthorityVO::getType));
            List<SysAuthorityVO> newVos = new ArrayList<>();
            for (Integer authType : authGroup.keySet()) {
                BaseDic.AuthorityType authorityType = XjEnumUtil.getByCode(BaseDic.AuthorityType.class, authType);
                SysAuthorityVO sysAuthorityVO = new SysAuthorityVO();
                sysAuthorityVO.setId("0");
                sysAuthorityVO.setPid("0");
                sysAuthorityVO.setName(authorityType.getDesc());
                List<SysAuthorityVO> sysAuthorityVOS = authGroup.get(authType);
                sysAuthorityVOS = sysAuthorityVOS.stream().sorted(Comparator.comparing(SysAuthorityVO::getUrl)).collect(Collectors.toList());
                if (authorityType.equals(BaseDic.AuthorityType.V2) || authorityType.equals(BaseDic.AuthorityType.V3)) {
                    sysAuthorityVO.setChildren(sysAuthorityVOS);
                    newVos.add(sysAuthorityVO);
                } else {
                    // 设置第二层分组 (根据接口路径生成的 groupName)
                    Map<String, List<SysAuthorityVO>> authTwoGroup = sysAuthorityVOS.stream().collect(Collectors.groupingBy(SysAuthorityVO::getGroupName));
                    authTwoGroup = XjMapSortUtil.sortByKey(authTwoGroup, true);
                    List<SysAuthorityVO> newTwoVos = new ArrayList<>();
                    for (String groupName : authTwoGroup.keySet()) {
                        SysAuthorityVO sysAuthorityTwoVO = new SysAuthorityVO();
                        sysAuthorityTwoVO.setId("0");
                        sysAuthorityTwoVO.setPid("0");
                        sysAuthorityTwoVO.setName(groupName);
                        sysAuthorityTwoVO.setChildren(authTwoGroup.get(groupName));
                        newTwoVos.add(sysAuthorityTwoVO);
                    }
                    sysAuthorityVO.setChildren(newTwoVos);
                    newVos.add(sysAuthorityVO);
                }
            }
            return newVos;
        }
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean upd(String id, SysAuthorityDTO dto) {
        // 更新
        SysAuthority entity = dto.convert(SysAuthority.class);
        entity.setId(id);
        boolean b = this.updateById(entity);
        this.delCache();
        return b;
    }


    /**
     * 接口自动扫描（1、项目启动时自动执行   2、设置了权限授权状态更新
     * <p>
     * 扫描添加接口信息，扫描启动类下的所有包
     * 存在修改（不修改原数据的禁用启动和权限状态,防止重启项目时修改被还原）
     * 不存在添加
     * 多余的生成
     * </p>
     *
     * @return void
     * @date 2019/11/25 0025 9:02
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    @XjDistributedLock(waitTime = 0, leaseTime = 10L)
    public Boolean refreshAuthAll() {
        initAuthScanningHandle.refreshAuthDb();
        this.delCache();
        return true;
    }


    @Override
    public SysAuthVO findAuthByUri(String method, String methodUri) {
        String prefix = redisUtil.getPrefix(false);
        String redisCacheKey = prefix + CacheKey.AUTH_MAP_ALL;
        // 写入权限缓存
        this.putAuthAllCache(redisCacheKey);
        // 查询权限数据
        String authCacheKey = XjAuthCacheKeyUtil.get(method, methodUri);
        Object o = redisUtil.hGet(redisCacheKey, authCacheKey);
        return JSON.parseObject(JSON.toJSONString(o), SysAuthVO.class);
    }


    @Override
    public boolean isAuthByRoleCodesAndUri(List<String> roleCodes, String method, String methodUri) {
        if (roleCodes == null || roleCodes.size() == 0) {
            return false;
        }
        String prefix = redisUtil.getPrefix(false);
        Map<String, String> roleIdsByCodes = sysRoleDao.findRoleIdsByCodes(roleCodes);
        // 写入角色权限缓存
        this.putRoleCodesAuth(prefix, roleIdsByCodes);
        // 判断是否有权限 通过角色 code 判断当前用户是否有权限
        boolean isAuth = false;
        for (String roleCode : roleIdsByCodes.keySet()) {
            String redisCacheKey = prefix + CacheKey.GATEWAY_ROLE_AUTH_MAPS + "::" + roleCode;
            String authCacheKey = XjAuthCacheKeyUtil.get(method, methodUri);
            Object obj = redisUtil.hGet(redisCacheKey, authCacheKey);
            if (obj != null) {
                isAuth = true;
                break;
            }
        }
        return isAuth;
    }


    /**
     * 写入接口权限缓存
     *
     * @param redisCacheKey
     */
    private void putAuthAllCache(String redisCacheKey) {
        boolean b = redisUtil.hasKey(redisCacheKey);
        if (b) {
            return;
        }
        List<SysAuthority> authorityList = this.list(new LambdaQueryWrapper<SysAuthority>()
                .orderByDesc(SysAuthority::getType)
                .orderByDesc(SysAuthority::getMethod)
                .ne(SysAuthority::getPid, "0")
        );
        List<SysAuthVO> vos = XjBeanUtil.listVo(authorityList, SysAuthVO.class);
        Map<String, Object> authMaps = new HashMap<>();
        vos.forEach(p -> authMaps.put(XjAuthCacheKeyUtil.get(p.getMethod(), p.getUrl()), p));
        redisUtil.hPutAll(redisCacheKey, authMaps);
    }


    /**
     * 写入角色权限缓存数据
     *
     * @param prefix
     * @param roleIdsByCodes
     */
    private void putRoleCodesAuth(String prefix, Map<String, String> roleIdsByCodes) {
        for (String roleCode : roleIdsByCodes.keySet()) {
            String redisCacheKey = prefix + CacheKey.GATEWAY_ROLE_AUTH_MAPS + "::" + roleCode;
            // 缓存是否已存在
            boolean b = redisUtil.hasKey(redisCacheKey);
            if (b) {
                continue;
            }
            String roleId = roleIdsByCodes.get(roleCode);
            List<String> authIds = sysRoleAuthDao.findAuthIdsByRoleId(roleId);
            // 当前角色没有权限
            if (authIds.size() == 0) {
                continue;
            }
            // 有权限写入缓存
            List<SysAuthority> authorityList = this.list(new LambdaQueryWrapper<SysAuthority>()
                    .orderByDesc(SysAuthority::getType)
                    .orderByDesc(SysAuthority::getMethod)
                    .ne(SysAuthority::getPid, "0")
                    .in(authIds.size() > 0, SysAuthority::getId, authIds)
            );
            List<SysAuthVO> vos = XjBeanUtil.listVo(authorityList, SysAuthVO.class);
            Map<String, Object> authMaps = new HashMap<>();
            vos.forEach(p -> authMaps.put(XjAuthCacheKeyUtil.get(p.getMethod(), p.getUrl()), true));
            redisUtil.hPutAll(redisCacheKey, authMaps);
        }
    }

    /**
     * 接口权限缓存清除
     * <p>
     * 在多租户的情况下, 让其能清除所有租户的缓存
     * <p/>
     */
    private void delCache() {
        Set<String> keys = redisUtil.getKeysByLike(CacheKey.AUTH_MAP_ALL);
        redisUtil.delete(keys);
    }

    /**
     * 获取接口分组
     */
    private String getGroupName(String uri) {
        if (uri.charAt(0) == '/') {
            uri = uri.substring(1);
        }
        String[] urlArray = uri.split("\\/");
        String groupName = "other";
        if (urlArray.length >= 3) {
            groupName = urlArray[0] + "/" + urlArray[1] + "/" + urlArray[2];
        }
        return groupName;
    }
}

